package main

import (
	"errors"
	"fmt"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"gitee.com/liuqiuming/gps"
	"github.com/beevik/etree"
	"github.com/tealeg/xlsx"
	"gopkg.in/ini.v1"
)

var (
	xlsxFileName string //表格文件名
	kmlFileName  string //输出kml文件名
	wg           sync.WaitGroup
	doc          *etree.Document
	lock         sync.RWMutex
)

//表格中必需列的列号
var (
	cellNameColNo   int //小区名列号
	lonColNo        int //经度列号
	latColNo        int //纬度列号
	azColNo         int //方位角列号
	freqColNo       int //频段列号
	outInColNo      int //室内外标识列号
	covertpColNo    int //覆盖区域类型列号
	pciColNo        int //PCI列号
	updateDateColNo int //更新日期列号，此列不必需，但因为EXCEL日期格式，需特殊处理
	testDateColNo   int //5G新增验证测试日期
)

//配置文件各个param
var (
	cfgCellName []string
	cfgLon      []string
	cfgLat      []string
	cfgAz       []string
	cfgFreq     []string
	cfgCovTp    []string
	cfgIndoor   []string
	cfgPci      []string
	r1f1800     int //1.8G市区扇形半径
	r2f1800     int //1.8G郊区扇形半径
	r3f1800     int //1.8G农村扇形半径
	r1f2100     int
	r2f2100     int
	r3f2100     int
	r1f800      int
	r2f800      int
	r3f800      int
	r1f3500     int
	r2f3500     int
	r3f3500     int
)
var layers []string //图层目录列表

//根据列号判断该列是否为必需列
func isNeeded(colNo int) bool {
	switch colNo {
	case cellNameColNo:
		return true
	case lonColNo:
		return true
	case latColNo:
		return true
	case azColNo:
		return true
	case freqColNo:
		return true
	case outInColNo:
		return true
	case covertpColNo:
		return true
	case pciColNo:
		return true
	default:
		return false
	}
}

//根据列的名称查EXCEL表格得到列名，如果没找到返回错误
func getColNo(head *xlsx.Row, maxCol int, name []string) (colNo int, err error) {
	for i := 0; i < maxCol; i++ {
		for j := 0; j < len(name); j++ {
			if head.Cells[i].Value == name[j] {
				return i, nil
			}
		}
	}
	return -1, errors.New("没有找到该列")
}

//根据频段和覆盖类型计算得到扇形的半径
func getRadius(row *xlsx.Row) (r float64) {
	freq := row.Cells[freqColNo].Value
	covType := row.Cells[covertpColNo].Value
	switch freq {
	case "1":
		switch covType {
		case "市区":
			r = float64(r1f2100)
		case "郊区":
			r = float64(r2f2100)
		case "乡镇":
			r = float64(r2f2100)
		case "农村":
			r = float64(r3f2100)
		default:
			r = 120
		}
	case "3":
		fallthrough
	case "1.8G":
		switch covType {
		case "市区":
			r = float64(r1f1800)
		case "郊区":
			r = float64(r2f1800)
		case "乡镇":
			r = float64(r2f1800)
		case "农村":
			r = float64(r3f1800)
		default:
			r = 200
		}
	case "5":
		fallthrough
	case "8":
		fallthrough
	case "800M":
		fallthrough
	case "900M":
		switch covType {
		case "市区":
			r = float64(r1f800)
		case "郊区":
			r = float64(r2f800)
		case "乡镇":
			r = float64(r2f800)
		case "农村":
			r = float64(r3f800)
		default:
			r = 250
		}
	case "3.5G":
		switch covType {
		case "城区":
			r = float64(r1f3500)
		case "市区":
			r = float64(r1f3500)
		case "郊区":
			r = float64(r2f3500)
		case "农村":
			r = float64(r3f3500)
		default:
			r = 150
		}
	case "2.1G":
		switch covType {
		case "城区":
			r = 180
		default:
			r = 200
		}
	default:
		r = 300
	}
	return r
}

//检查表格数据
func checkExcel(sheet *xlsx.Sheet, maxRow int, maxCol int) {
	fmt.Println("检查Excel表格数据")
	//检查是否有所需的各列
	var err error
	cellNameColNo, err = getColNo(sheet.Rows[0], maxCol, cfgCellName)
	if err != nil {
		fmt.Println("没有找到小区名这列！")
		os.Exit(1)
	}
	lonColNo, err = getColNo(sheet.Rows[0], maxCol, cfgLon)
	if err != nil {
		fmt.Println("没有找到扇区经度这列！")
		os.Exit(1)
	}
	latColNo, err = getColNo(sheet.Rows[0], maxCol, cfgLat)
	if err != nil {
		fmt.Println("没有找到扇区纬度这列！")
		os.Exit(1)
	}
	azColNo, err = getColNo(sheet.Rows[0], maxCol, cfgAz)
	if err != nil {
		fmt.Println("没有找到天线方位角这列！")
		os.Exit(1)
	}
	freqColNo, err = getColNo(sheet.Rows[0], maxCol, cfgFreq)
	if err != nil {
		fmt.Println("没有找到频段指示这列！")
		os.Exit(1)
	}
	outInColNo, err = getColNo(sheet.Rows[0], maxCol, cfgIndoor)
	if err != nil {
		fmt.Println("没有找到室内外标识这列！")
		os.Exit(1)
	}
	covertpColNo, err = getColNo(sheet.Rows[0], maxCol, cfgCovTp)
	if err != nil {
		fmt.Println("没有找到扇区覆盖区域类型这列！")
		os.Exit(1)
	}
	pciColNo, err = getColNo(sheet.Rows[0], maxCol, cfgPci)
	if err != nil {
		fmt.Println("没有找到物理小区标识这列！")
		os.Exit(1)
	}
	updateDateColNo, err = getColNo(sheet.Rows[0], maxCol, []string{"最近更新日期", "开通时间"})
	if err != nil {
		fmt.Println("没有找到最近更新日期这列，将不会处理日期格式！")
		//os.Exit(1)
	}
	testDateColNo, err = getColNo(sheet.Rows[0], maxCol, []string{"单验时间"})
	if err != nil {
		fmt.Println("没有找到单验时间这列，将不会处理日期格式！")
		//os.Exit(1)
	}
	//生成图层目录结构，有几个频段生成几个目录
	layers = make([]string, 10)
	for i := 1; i < maxRow; i++ {
		row := sheet.Rows[i]
		freq := row.Cells[freqColNo].Value
		switch freq {
		case "1":
			fallthrough
		case "2.1G":
			for _, v := range layers {
				if v != "2.1G" {
					layers[0] = "2.1G"
				}
			}
		case "3":
			fallthrough
		case "1.8G":
			for _, v := range layers {
				if v != "1.8G" {
					layers[1] = "1.8G"
				}
			}
		case "5":
			fallthrough
		case "800M":
			for _, v := range layers {
				if v != "800M" {
					layers[2] = "800M"
				}
			}
		case "8":
			fallthrough
		case "900M":
			for _, v := range layers {
				if v != "900M" {
					layers[3] = "900M"
				}
			}
		case "3.5G":
			for _, v := range layers {
				if v != "3.5G" {
					layers[4] = "3.5G"
				}
			}

		}
		if len(row.Cells) < maxCol {
			fmt.Printf("错误：第%d行最后一列为空单元格！\n", i)
			os.Exit(1)
		}
		//检查经纬度列数据是否超过边界
		lon, _ := strconv.ParseFloat(row.Cells[lonColNo].Value, 64)
		lat, _ := strconv.ParseFloat(row.Cells[latColNo].Value, 64)
		p := gps.Point{Lon: lon, Lat: lat}
		if !gps.PointIsInBorder(p, border) {
			fmt.Printf("第%d行错误：Lon= %f Lat= %f 不在边界内！", i, p.Lon, p.Lat)
			os.Exit(1)
		}

	}
	fmt.Println("检查完成")
	fmt.Println("可以输出以下图层：", layers)
}

func main() {
	//读取命令行参数，得到表格文件名。如果没有表格文件名，则默认使用cells.xlsx
	xlsxFileName = "cells.xlsx"
	kmlFileName = "cells.kml"
	if len(os.Args) == 2 {
		xlsxFileName = os.Args[1]
	}
	if len(os.Args) > 2 {
		fmt.Println("usage: Cell2Kml.exe cells.xlsx")
		return
	}
	//读取配置数据
	cfg, err := ini.Load("config.ini")
	if err != nil {
		fmt.Printf("没有找到配置文件: %v\t将使用默认设置！", err)
		cfgCellName = []string{"小区名", "CellName"}
		cfgLon = []string{"扇区经度", "Longitude"}
		cfgLat = []string{"扇区纬度", "Latitude"}
		cfgAz = []string{"天线方位角", "Azimuth"}
		cfgFreq = []string{"频段指示", "FrequencyBandInfo"}
		cfgIndoor = []string{"室内外标识", "isOutdoor"}
		cfgCovTp = []string{"扇区覆盖区域类型", "区域类型"}
		cfgPci = []string{"物理小区标识", "PCI"}

		r1f2100 = 80
		r2f2100 = 200
		r3f2100 = 300
		r1f1800 = 100
		r2f1800 = 250
		r3f1800 = 350
		r1f800 = 130
		r2f800 = 300
		r3f800 = 500
		r1f3500 = 120
		r2f3500 = 150
		r3f3500 = 300
		//os.Exit(1)
	}
	//找到配置文件，加载表格列名
	cfgCellName = strings.Split(cfg.Section("columns").Key("cellName").String(), ",")
	cfgLon = strings.Split(cfg.Section("columns").Key("lon").String(), ",")
	cfgLat = strings.Split(cfg.Section("columns").Key("lat").String(), ",")
	cfgAz = strings.Split(cfg.Section("columns").Key("az").String(), ",")
	cfgFreq = strings.Split(cfg.Section("columns").Key("freq").String(), ",")
	cfgCovTp = strings.Split(cfg.Section("columns").Key("coverType").String(), ",")
	cfgIndoor = strings.Split(cfg.Section("columns").Key("inDoor").String(), ",")
	cfgPci = strings.Split(cfg.Section("columns").Key("pci").String(), ",")

	if r1f800, err = cfg.Section("radius").Key("r1f800").Int(); err != nil {
		fmt.Println("读取INI中参数r1f800失败！")
		r1f800 = 130
	}
	if r2f800, err = cfg.Section("radius").Key("r2f800").Int(); err != nil {
		fmt.Println("读取INI中参数r2f800失败！")
		r2f800 = 300
	}
	if r3f800, err = cfg.Section("radius").Key("r3f800").Int(); err != nil {
		fmt.Println("读取INI中参数r3f800失败！")
		r3f800 = 500
	}

	if r1f1800, err = cfg.Section("radius").Key("r1f1800").Int(); err != nil {
		fmt.Println("读取INI中参数r1f1800失败！")
		r1f1800 = 100
	}
	if r2f1800, err = cfg.Section("radius").Key("r2f1800").Int(); err != nil {
		fmt.Println("读取INI中参数r2f1800失败！")
		r2f1800 = 250
	}
	if r3f1800, err = cfg.Section("radius").Key("r3f1800").Int(); err != nil {
		fmt.Println("读取INI中参数r3f1800失败！")
		r3f1800 = 350
	}

	if r1f2100, err = cfg.Section("radius").Key("r1f2100").Int(); err != nil {
		fmt.Println("读取INI中参数r1f2100失败！")
		r1f2100 = 80
	}
	if r2f2100, err = cfg.Section("radius").Key("r2f2100").Int(); err != nil {
		fmt.Println("读取INI中参数r2f2100失败！")
		r2f2100 = 200
	}
	if r3f2100, err = cfg.Section("radius").Key("r3f2100").Int(); err != nil {
		fmt.Println("读取INI中参数r3f2100失败！")
		r3f2100 = 300
	}

	if r1f3500, err = cfg.Section("radius").Key("r1f3500").Int(); err != nil {
		fmt.Println("读取INI中参数r1f3500失败！")
		r1f3500 = 120
	}
	if r2f3500, err = cfg.Section("radius").Key("r2f3500").Int(); err != nil {
		fmt.Println("读取INI中参数r2f3500失败！")
		r2f3500 = 150
	}
	if r3f3500, err = cfg.Section("radius").Key("r3f3500").Int(); err != nil {
		fmt.Println("读取INI中参数r3f3500失败！")
		r3f3500 = 300
	}

	//fmt.Println(r1f800, r2f800, r3f800, r1f1800, r2f1800, r3f1800, r1f2100, r2f2100, r3f2100, r1f3500, r2f3500, r3f3500)
	//读取表格数据
	fmt.Println("读取Excel表格:", xlsxFileName)
	xlsxFile, err := xlsx.OpenFile(xlsxFileName)
	if err != nil {
		fmt.Printf("open %s failed: %s\n", xlsxFileName, err)
		return
	}
	sheet1 := xlsxFile.Sheets[0]
	maxRow := sheet1.MaxRow
	maxCol := sheet1.MaxCol
	fmt.Println("读取完成")

	checkExcel(sheet1, maxRow, maxCol) //检查表格数据，并设置重要字段列号

	//读取KML模板文档内容
	doc = etree.NewDocument()
	if err := doc.ReadFromString(strKml); err != nil {
		panic(err)
	}

	//设置目录结构
	kml := doc.SelectElement("kml")
	d := kml.SelectElement("Document")
	ltefd := d.SelectElement("Folder")
	lteName := ltefd.SelectElement("name")
	t := time.Now()
	strName := "基站图层" + t.Format("2006-01-02")
	lteName.SetText(strName)
	var fd800Info, fd800Sec, fd900Info, fd900Sec, fd1800Info, fd1800Sec, fd2100Info, fd2100Sec, fd3500Info, fd3500Sec *etree.Element

	for _, v := range layers {
		switch v {
		case "800M":
			//800M图层目录
			fdtc800 := ltefd.CreateElement("Folder")
			tc800Name := fdtc800.CreateElement("name")
			tc800Name.CreateText("800M图层")
			fd800Info = fdtc800.CreateElement("Folder") //800M小区信息图层*********fd800Info
			fd800InfoName := fd800Info.CreateElement("name")
			fd800InfoName.CreateText("小区信息")
			fd800Sec = fdtc800.CreateElement("Folder") //800M小区图形图层**********fd800Sec
			fd800SecName := fd800Sec.CreateElement("name")
			fd800SecName.CreateText("小区图形")
		case "900M":
			//900M图层目录
			fdtc900 := ltefd.CreateElement("Folder")
			tc900Name := fdtc900.CreateElement("name")
			tc900Name.CreateText("900M图层")
			fd900Info = fdtc900.CreateElement("Folder") //900M小区信息图层*********fd900Info
			fd900InfoName := fd900Info.CreateElement("name")
			fd900InfoName.CreateText("小区信息")
			fd900Sec = fdtc900.CreateElement("Folder") //900M小区图形图层**********fd900Sec
			fd900SecName := fd900Sec.CreateElement("name")
			fd900SecName.CreateText("小区图形")
		case "1.8G":
			//1.8G图层
			fdtc1800 := ltefd.CreateElement("Folder")
			tc1800Name := fdtc1800.CreateElement("name")
			tc1800Name.CreateText("1.8G图层")
			fd1800Info = fdtc1800.CreateElement("Folder") //1.8G小区信息图层**********fd1800Info
			fd1800InfoName := fd1800Info.CreateElement("name")
			fd1800InfoName.CreateText("小区信息")
			fd1800Sec = fdtc1800.CreateElement("Folder") //1.8G小区图形图层*********fd1800Sec
			fd1800SecName := fd1800Sec.CreateElement("name")
			fd1800SecName.CreateText("小区图形")
		case "2.1G":
			//2.1G图层
			fdtc2100 := ltefd.CreateElement("Folder")
			tc2100Name := fdtc2100.CreateElement("name")
			tc2100Name.CreateText("2.1G图层")
			fd2100Info = fdtc2100.CreateElement("Folder") //2.1G小区信息图层**********fd2100Info
			fd2100InfoName := fd2100Info.CreateElement("name")
			fd2100InfoName.CreateText("小区信息")
			fd2100Sec = fdtc2100.CreateElement("Folder") //2.1G小区图形图层*********fd2100Sec
			fd2100SecName := fd2100Sec.CreateElement("name")
			fd2100SecName.CreateText("小区图形")
		case "3.5G":
			//3.5G图层
			fdtc3500 := ltefd.CreateElement("Folder")
			tc3500Name := fdtc3500.CreateElement("name")
			tc3500Name.CreateText("3.5G图层")
			fd3500Info = fdtc3500.CreateElement("Folder") //3.5G小区信息图层**********fd3500Info
			fd3500InfoName := fd3500Info.CreateElement("name")
			fd3500InfoName.CreateText("小区信息")
			fd3500Sec = fdtc3500.CreateElement("Folder") //3.5G小区图形图层*********fd3500Sec
			fd3500SecName := fd3500Sec.CreateElement("name")
			fd3500SecName.CreateText("小区图形")

		}
	}

	//循环读取一行数据，然后开启worker协程完成计算和写入Kml
	for i := 1; i < maxRow; i++ {
		fmt.Printf("正在处理第%d 行\r", i)
		head := sheet1.Rows[0]
		row := sheet1.Rows[i]
		freq := row.Cells[freqColNo].Value
		switch freq {
		case "1":
			fallthrough
		case "2.1G": //5G新增
			wg.Add(2)
			go infoWorker(row, fd2100Info)
			go sectorWorker(head, row, maxCol, fd2100Sec)
		case "3":
			fallthrough
		case "1.8G":
			wg.Add(2)
			go infoWorker(row, fd1800Info)
			go sectorWorker(head, row, maxCol, fd1800Sec)
		case "5":
			fallthrough
		case "800M":
			wg.Add(2)
			go infoWorker(row, fd800Info)
			go sectorWorker(head, row, maxCol, fd800Sec)
		case "8":
			fallthrough
		case "900M":
			wg.Add(2)
			go infoWorker(row, fd900Info)
			go sectorWorker(head, row, maxCol, fd900Sec)
		case "3.5G": //5G新增
			wg.Add(2)
			go infoWorker(row, fd3500Info)
			go sectorWorker(head, row, maxCol, fd3500Sec)
		default:
			fmt.Println("第", i, "行频段指示数据错误！频段数据必须为以下：800M,1.8G,2.1G,3.5G等。")
			os.Exit(1)
		}
	}

	//等待worker完成，将kml结构体写入磁盘文件。
	wg.Wait()
	fmt.Printf("处理完成%d行，写入文件：%s\n", maxRow, kmlFileName)
	doc.Indent(2)
	err = doc.WriteToFile(kmlFileName)
	if err != nil {
		panic(err)
	}
	//fmt.Println("按回车退出")
	//var s string
	//fmt.Scanln(&s)

}
